package com.iplay.service.order;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.dangdang.ddframe.rdb.sharding.id.generator.IdGenerator;
import com.iplay.component.date.FeastBookingDateFormatter;
import com.iplay.component.naming.UUIDNamingStrategy;
import com.iplay.component.util.DelimiterUtils;
import com.iplay.dao.order.OrderContractDAO;
import com.iplay.dao.order.OrderDAO;
import com.iplay.dao.order.OrderPaymentDAO;
import com.iplay.dto.cashback.CashbackDTO;
import com.iplay.dto.cashback.CashbackStrategyCustomizationDTO;
import com.iplay.dto.cashback.CashbackStrategyDTO;
import com.iplay.dto.order.OrderContractDTO;
import com.iplay.dto.order.OrderDTO;
import com.iplay.dto.order.OrderPaymentDTO;
import com.iplay.dto.order.SimplifiedOrderDTO;
import com.iplay.dto.order.SimplifiedOrderForAdminDTO;
import com.iplay.entity.hotel.BanquetHallDO;
import com.iplay.entity.hotel.HotelDO;
import com.iplay.entity.order.ApprovalStatus;
import com.iplay.entity.order.ApprovalStatus.ModifiableApprovalStatus;
import com.iplay.entity.order.OrderContractDO;
import com.iplay.entity.order.OrderDO;
import com.iplay.entity.order.OrderDocIdApprovalStatusDO;
import com.iplay.entity.order.OrderIdParticipantStatusDO;
import com.iplay.entity.order.OrderPaymentDO;
import com.iplay.entity.order.OrderPaymentPaidApprovalStatusDO;
import com.iplay.entity.order.OrderStatus;
import com.iplay.entity.user.Role;
import com.iplay.service.cashback.CashbackService;
import com.iplay.service.exception.InvalidRequestParametersException;
import com.iplay.service.exception.ResourceForbiddenException;
import com.iplay.service.exception.ResourceNotFoundException;
import com.iplay.service.hotel.BanquetHallService;
import com.iplay.service.notification.core.NotificationService;
import com.iplay.service.notification.type.order.type.OrderCanceledMessage;
import com.iplay.service.notification.type.order.type.OrderCashbackMessage;
import com.iplay.service.notification.type.order.type.OrderContractStatusUpdatedMessage;
import com.iplay.service.notification.type.order.type.OrderContractUpdatedMessage;
import com.iplay.service.notification.type.order.type.OrderCreatedMessage;
import com.iplay.service.notification.type.order.type.OrderFeastingDateUpdatedMessage;
import com.iplay.service.notification.type.order.type.OrderManagerUpdatedMessage;
import com.iplay.service.notification.type.order.type.OrderPaymentStatusUpdatedMessage;
import com.iplay.service.notification.type.order.type.OrderPaymentUpdatedMessage;
import com.iplay.service.notification.type.order.OrderMessageEntity;
import com.iplay.service.order.mail.HotelReservation;
import com.iplay.service.order.mail.OrderMailService;
import com.iplay.service.order.query.OrderQueryByStatusAndParticipant;
import com.iplay.service.query.QueryBuilder;
import com.iplay.service.query.specification.QuerySepcification;
import com.iplay.service.storage.StorageService;
import com.iplay.service.user.SimplifiedUser;
import com.iplay.service.user.UserContrains;
import com.iplay.service.user.UserService;
import com.iplay.service.user.userInfo.UserInfoService;
import com.iplay.vo.cashback.CashbackStrategyCustomizationVO;
import com.iplay.vo.cashback.CashbackStrategyVO;
import com.iplay.vo.order.OrderStatusVO;
import com.iplay.vo.order.PostContractVO;
import com.iplay.vo.order.PostPaymentVO;
import com.iplay.vo.order.PostReservationVO;
import com.iplay.vo.order.query.OrderQueryVO;
import com.iplay.web.resource.ResourcesUriBuilder;

@Service
public class OrderServiceImpl implements OrderService {

	@Autowired
	private UserService userService;

	@Autowired
	private OrderDAO orderDAO;

	@Autowired
	private OrderContractDAO orderContractDAO;

	@Autowired
	private OrderPaymentDAO orderPaymentDAO;

	@Autowired
	private BanquetHallService banquetHallService;

	@Autowired
	private StorageService storageService;

	@Autowired
	private IdGenerator idGenerator;

	@Autowired
	private FeastBookingDateFormatter dateFormatter;

	@Autowired
	private CashbackService cashbackService;

	@Autowired
	private UserInfoService userInfoService;

	@Autowired
	private QueryBuilder<OrderQueryVO> queryBuilder;

	@Autowired
	private NotificationService notificationService;

	@Autowired
	private OrderMailService orderMailService;

	@Override
	@Transactional
	public int addReservation(SimplifiedUser authenticatedUser, PostReservationVO vo) {
		BanquetHallDO banquetHall = banquetHallService.findBHById(vo.getBanquetHallId())
				.orElseThrow(() -> new ResourceNotFoundException(
						"Banquet hall with id: " + vo.getBanquetHallId() + " doesn't exist"));
		OrderDO order = new OrderDO();
		if (!StringUtils.isEmpty(vo.getRecommender())) {
			boolean[] recommenderExists = { false };
			userService.findSimplifiedUserByUsername(vo.getRecommender())
					.filter(recommender -> recommender.getRole() != Role.ADMIN).ifPresent(recommender -> {
						order.setRecommender(recommender.getUsername());
						order.setRecommenderId(recommender.getId());
						recommenderExists[0] = true;
					});
			if (!recommenderExists[0])
				return -1;
		}
		BeanUtils.copyProperties(vo, order);
		order.setBanquetHallId(banquetHall.getId());
		order.setBanquetHall(banquetHall.getName());
		HotelDO hotel = banquetHall.getHotelDO();
		order.setHotelId(hotel.getId());
		order.setHotel(hotel.getName());
		order.setCustomerId(authenticatedUser.getUserId());
		order.setCustomer(authenticatedUser.getUsername());
		order.setOrderStatus(OrderStatus.CONSULTING);
		order.setOrderTime(System.currentTimeMillis());
		order.setOrderNumber(generateOrderNumber());
		order.setLastUpdated(order.getOrderTime());
		OrderDO savedOrder = orderDAO.save(order);

		OrderCreatedMessage message = new OrderCreatedMessage(new OrderMessageEntity(savedOrder.getId(),
				savedOrder.getHotel(), savedOrder.getCustomer(), savedOrder.getRecommender(), savedOrder.getManager(), savedOrder.getOrderStatus().getCategory()));
		notificationService.sendMessages(message.getOutgoingMessage());

		HotelReservation reservation = new HotelReservation(order.getCustomer(), order.getHotel(),
				order.getBanquetHall(), order.getTables(), order.getCandidateDates(), order.getRecommender(),
				order.getContact(), order.getPhone());
		orderMailService.sendOrderCreatedMail(UserContrains.SUPER_ADMIN_USERNAME, null, reservation);
		orderMailService.sendOrderCreatedMail(order.getRecommender(), RoleInOrder.RECOMMENDER, reservation);
		orderMailService.sendOrderCreatedMail(order.getCustomer(), RoleInOrder.CUSTOMER, reservation);
		return savedOrder.getId();
	}

	@Override
	public boolean fillManager(SimplifiedUser authenticatedUser, int orderId, String manager) {
		boolean[] rs = { false };
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if (order.getOrderStatus() != OrderStatus.CONSULTING || authenticatedUser.getUserId() != order.getCustomerId())
			throw new ResourceForbiddenException(
					"Order status must be " + OrderStatus.CONSULTING + " or you don't have authority!");
		userService.findSimplifiedUserByUsername(manager).filter(u -> u.getRole() == Role.MANAGER).ifPresent(u -> {
			orderDAO.updateManager(order.getId(), u.getId(), u.getUsername(), System.currentTimeMillis());
			rs[0] = true;
		});
		if (rs[0]) {
			notificationService
					.sendMessages(new OrderManagerUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());

			OrderDO orderDO = orderDAO.findOne(orderId);
			HotelReservation reservation = new HotelReservation(orderDO.getCustomer(), orderDO.getHotel(),
					orderDO.getBanquetHall(), orderDO.getTables(), orderDO.getCandidateDates(),
					orderDO.getRecommender(), orderDO.getContact(), orderDO.getPhone());
			orderMailService.sendOrderCreatedMail(manager, RoleInOrder.MANAGER, reservation);
		}
		return rs[0];
	}

	@Override
	public boolean fillFeastingDate(SimplifiedUser authenticatedUser, int orderId, String date) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if (order.getOrderStatus() != OrderStatus.CONSULTING || authenticatedUser.getUserId() != order.getCustomerId())
			throw new ResourceForbiddenException(
					"Order status must be " + OrderStatus.CONSULTING + " or you don't have authority!");
		orderDAO.updateFeastingDate(orderId, date, System.currentTimeMillis());
		
		notificationService
		.sendMessages(new OrderFeastingDateUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());
		
		return true;
	}

	@Override
	public OrderStatus moveToNextStatus(int orderId) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		OrderStatus next = order.getOrderStatus().next();
		if (next == null)
			throw new ResourceForbiddenException("Order in status CANCELED or DONE can't move to next!");
		orderDAO.updateStatus(orderId, next);
		if (next == OrderStatus.TO_BE_REVIEWD) {
			OrderPaymentPaidApprovalStatusDO payment = orderPaymentDAO.findAmountPaidApprovalStatusById(order.getId());
			if (payment != null) {
				cashbackService.findCashbackStrategyByOrderId(orderId).ifPresent(strategy -> {
					double totalPayment = payment.getAmountPaid();
					CashbackStrategyDTO strategyDTO = strategy.getCashbackStrategy();
					userInfoService.updateCashbackInfo(order.getCustomerId(),
							totalPayment * strategyDTO.getPercentageForCustomer() / 100,
							0 - totalPayment * strategyDTO.getPercentageForCustomer() / 100);
					userInfoService.updateCashbackInfo(order.getManagerId(),
							totalPayment * strategyDTO.getPercentageForManager() / 100,
							0 - totalPayment * strategyDTO.getPercentageForManager() / 100);
					userInfoService.updateCashbackInfo(order.getRecommenderId(),
							totalPayment * strategyDTO.getPercentageForRecommender() / 100,
							0 - totalPayment * strategyDTO.getPercentageForRecommender() / 100);

					notificationService
					.sendMessages(new OrderCashbackMessage(getOrderMessageEntity(order)).getOutgoingMessage());
				});
			}
		}
		return next;
	}

	@Override
	public OrderStatus updateStatus(int orderId, OrderStatus newOrderStatus) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		orderDAO.updateStatus(order.getId(), newOrderStatus);
		if (newOrderStatus == OrderStatus.CANCELED) {
			notificationService
			.sendMessages(new OrderCanceledMessage(getOrderMessageEntity(order)).getOutgoingMessage());
		}
		return newOrderStatus;
	}

	@Override
	@Transactional
	public boolean fillPayment(SimplifiedUser authenticatedUser, int orderId, PostPaymentVO postPaymentVO) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if ((order.getOrderStatus() != OrderStatus.RESERVED && order.getOrderStatus() != OrderStatus.FEASTED)
				|| authenticatedUser.getUserId() != order.getCustomerId())
			throw new ResourceForbiddenException("Order status must be " + OrderStatus.RESERVED + " or "
					+ OrderStatus.FEASTED + " or you don't have authority!");
		OrderPaymentDO payment = orderPaymentDAO.findOne(order.getId());
		if (payment != null && payment.getApprovalStatus() != ApprovalStatus.PENDING
				&& payment.getApprovalStatus() != ApprovalStatus.DECLINED)
			throw new ResourceForbiddenException("Order payment must be in " + ApprovalStatus.PENDING + " status or "
					+ ApprovalStatus.DECLINED + " status!");

		String[] files = uploadFiles(postPaymentVO.getFiles());
		String[] deletedFiles = DelimiterUtils.split(postPaymentVO.getDeleted(),
				DelimiterUtils.GLOBAL_DEFAULT_DELIMITER);
		String paymentFiles = "";
		if (payment != null && (paymentFiles = payment.getPayment()) != null) {
			if (deletedFiles != null) {
				for (String deletedFile : deletedFiles) {
					paymentFiles = paymentFiles.replace(deletedFile + DelimiterUtils.GLOBAL_DEFAULT_DELIMITER, "");
				}
				storageService.delete(deletedFiles);
			}
		}
		if (files != null) {
			paymentFiles = paymentFiles == null ? ""
					: paymentFiles + DelimiterUtils.joinArray(files, DelimiterUtils.GLOBAL_DEFAULT_DELIMITER);
		}
		orderPaymentDAO.save(new OrderPaymentDO(order.getId(), postPaymentVO.getAmountPaid(), paymentFiles,
				System.currentTimeMillis(), ApprovalStatus.PENDING));

		// orderDAO.updateOrder(order.getId(), System.currentTimeMillis());
		// orderDAO.updateStatus(orderId, OrderStatus.FEASTED);
		orderDAO.updateStatusLastUpdatedPaymentStatus(orderId, OrderStatus.FEASTED, System.currentTimeMillis(),
				ApprovalStatus.PENDING);

		notificationService
		.sendMessages(new OrderPaymentUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());
		return true;
	}

	@Override
	@Transactional
	public boolean uploadContract(SimplifiedUser authenticatedUser, int orderId, PostContractVO vo) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if (order.getOrderStatus() != OrderStatus.CONSULTING || authenticatedUser.getUserId() != order.getCustomerId())
			throw new ResourceForbiddenException(
					"Order status must be " + OrderStatus.CONSULTING + " or you don't have authority!");
		OrderContractDO contract = orderContractDAO.findOne(order.getId());
		if (contract != null && contract.getApprovalStatus() != ApprovalStatus.PENDING
				&& contract.getApprovalStatus() != ApprovalStatus.DECLINED)
			throw new ResourceForbiddenException("Order contract must be in " + ApprovalStatus.PENDING + " status or "
					+ ApprovalStatus.DECLINED + " status!");

		String[] deletedFiles = DelimiterUtils.split(vo.getDeleted(), DelimiterUtils.GLOBAL_DEFAULT_DELIMITER);
		String[] files = uploadFiles(vo.getFiles());
		String contractFiles = "";
		if (contract != null && (contractFiles = contract.getContract()) != null) {
			if (deletedFiles != null) {
				for (String deletedFile : deletedFiles) {
					contractFiles = contractFiles.replace(deletedFile + DelimiterUtils.GLOBAL_DEFAULT_DELIMITER, "");
				}
				storageService.delete(deletedFiles);
			}
		}

		if (files != null) {
			contractFiles = (contractFiles == null ? "" : contractFiles)
					+ DelimiterUtils.joinArray(files, DelimiterUtils.GLOBAL_DEFAULT_DELIMITER);
		}

		orderContractDAO.save(
				new OrderContractDO(order.getId(), contractFiles, System.currentTimeMillis(), ApprovalStatus.PENDING));
		// orderDAO.updateOrder(order.getId(), System.currentTimeMillis());
		orderDAO.updateLastUpdatedContractStatus(orderId, System.currentTimeMillis(), ApprovalStatus.PENDING);

		notificationService
		.sendMessages(new OrderContractUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());

		return true;
	}

	private String[] uploadFiles(MultipartFile[] files) {
		if (files == null)
			return null;
		String[] filenames = new String[files.length];
		for (int i = 0; i < files.length; i++) {
			String filename = storageService.store(files[i], UUIDNamingStrategy.generateUUID());
			filenames[i] = filename;
		}
		return filenames;
	}

	private OrderIdParticipantStatusDO checkExistenceOfOrder(int orderId) {
		OrderIdParticipantStatusDO simplifiedOrder = orderDAO.findIdCustomerIdManagerIdRecommenderIdStatusById(orderId);
		if (simplifiedOrder == null)
			throw new ResourceNotFoundException("Order with id: " + orderId + " doesn't exist!");
		return simplifiedOrder;
		/*
		 * return new OrderDO(simplifiedOrder.getId(), simplifiedOrder.getCustomerId(),
		 * simplifiedOrder.getManagerId(), simplifiedOrder.getRecommenderId(),
		 * simplifiedOrder.getOrderStatus());
		 */
	}

	private OrderDocIdApprovalStatusDO checkExistenceOfOrderContract(int orderId) {
		OrderDocIdApprovalStatusDO simplifiedOrderContract = orderContractDAO.findIdApprovalStatusById(orderId);
		if (simplifiedOrderContract == null)
			throw new ResourceNotFoundException(
					"Order with id: " + orderId + " doesn't have contract information yet!");
		return simplifiedOrderContract;
	}

	private OrderPaymentPaidApprovalStatusDO checkExistenceOfOrderPayment(int orderId) {
		OrderPaymentPaidApprovalStatusDO simplifiedOrderContract = orderPaymentDAO
				.findAmountPaidApprovalStatusById(orderId);
		if (simplifiedOrderContract == null)
			throw new ResourceNotFoundException("Order with id: " + orderId + " doesn't have payment information yet!");
		return simplifiedOrderContract;
	}

	@Override
	public long generateOrderNumber() {
		return idGenerator.generateId().longValue();
	}

	@Override
	public Page<SimplifiedOrderDTO> listOrders(SimplifiedUser authenticatedUser, OrderStatusVO vo, Pageable pageable) {
		int currUser = authenticatedUser.getUserId();
		Page<OrderDO> page = null;
		try {
			System.out.println(getOrderStatusCollection(vo));
			page = orderDAO.findAll(new OrderQueryByStatusAndParticipant(getOrderStatusCollection(vo), currUser),
					pageable);
			return page.map(o -> {
				return new SimplifiedOrderDTO(o.getId(), o.getHotel(), o.getBanquetHall(), o.getContact(),
						o.getFeastingDate(), o.getTables(), o.getOrderStatus(),
						getRoleInOrder(currUser, o.getCustomerId(), o.getRecommenderId(), o.getManagerId()));
			});
		} catch (PropertyReferenceException e) {
			throw new InvalidRequestParametersException(e.getMessage());
		}
	}

	@Override
	public Optional<OrderDTO> findOrderById(SimplifiedUser authenticatedUser, int id) {
		OrderDTO orderDTO = null;
		OrderDO orderDO = orderDAO.findOne(id);
		if (orderDO != null) {
			if (orderDO.getCustomerId() != authenticatedUser.getUserId()
					&& orderDO.getManagerId() != authenticatedUser.getUserId()
					&& authenticatedUser.getRole() != Role.ADMIN)
				throw new ResourceForbiddenException("No authority to access the order!");
			orderDTO = new OrderDTO();
			orderDTO.setOrderNumber(String.valueOf(orderDO.getOrderNumber()));
			BeanUtils.copyProperties(orderDO, orderDTO);
			orderDTO.setOrderTime(dateFormatter.toOrderCreationTime(orderDO.getOrderTime()));
			orderDTO.setLastUpdated(dateFormatter.toOrderLastUpdated(orderDO.getLastUpdated()));
			orderDTO.setCandidateDates(
					DelimiterUtils.split(orderDO.getCandidateDates(), DelimiterUtils.GLOBAL_DEFAULT_DELIMITER));

			OrderContractDTO orderContractDTO = null;
			OrderContractDO orderContractDO = orderContractDAO.findOne(orderDO.getId());
			if (orderContractDO != null) {
				orderContractDTO = new OrderContractDTO(
						ResourcesUriBuilder.buildUris(orderContractDO.getContractAsArray()),
						orderContractDO.getApprovalStatus(),
						dateFormatter.toOrderLastUpdated(orderContractDO.getLastUpdated()));
			}
			orderDTO.setOrderContractDTO(orderContractDTO);

			OrderPaymentDTO orderPaymentDTO = null;
			OrderPaymentDO orderPaymentDO = orderPaymentDAO.findOne(orderDO.getId());
			if (orderPaymentDO != null) {
				orderPaymentDTO = new OrderPaymentDTO(orderPaymentDO.getAmountPaid(),
						ResourcesUriBuilder.buildUris(orderPaymentDO.getPaymentAsArray()),
						orderPaymentDO.getApprovalStatus(),
						dateFormatter.toOrderLastUpdated(orderPaymentDO.getLastUpdated()));
			}
			orderDTO.setOrderPaymentDTO(orderPaymentDTO);
		}
		return Optional.ofNullable(orderDTO);
	}

	@Override
	public Optional<CashbackStrategyCustomizationDTO> findCashbackStrategyByOrderId(int orderId,
			SimplifiedUser authenticatedUser) {
		OrderIdParticipantStatusDO orderDO = checkExistenceOfOrder(orderId);
		if (orderDO.getCustomerId() != authenticatedUser.getUserId()
				&& orderDO.getManagerId() != authenticatedUser.getUserId() && authenticatedUser.getRole() != Role.ADMIN)
			throw new ResourceForbiddenException("No authority to access the order!");
		return cashbackService.findCashbackStrategyByOrderId(orderId);
	}

	@Override
	public CashbackDTO findCashbackByOrderId(int orderId, SimplifiedUser authenticatedUser) {
		OrderIdParticipantStatusDO orderDO = checkExistenceOfOrder(orderId);
		if (orderDO.getCustomerId() != authenticatedUser.getUserId()
				&& orderDO.getManagerId() != authenticatedUser.getUserId() && authenticatedUser.getRole() != Role.ADMIN)
			throw new ResourceForbiddenException("No authority to access the order!");
		OrderPaymentPaidApprovalStatusDO simplifiedPaymentDO = orderPaymentDAO
				.findAmountPaidApprovalStatusById(orderId);
		if (simplifiedPaymentDO == null)
			return null;
		CashbackStrategyDTO cashbackStratey = null;
		Optional<CashbackStrategyCustomizationDTO> cashbackStrateyForOrder = cashbackService
				.findCashbackStrategyByOrderId(orderId);
		if (cashbackStrateyForOrder.isPresent())
			cashbackStratey = cashbackStrateyForOrder.get().getCashbackStrategy();
		return new CashbackDTO(orderId, simplifiedPaymentDO.getAmountPaid(), cashbackStratey);
	}

	@Override
	public boolean customizeCashbackStrategy(int orderId, CashbackStrategyVO cashbackStrategy) {
		checkExistenceOfOrder(orderId);
		return cashbackService
				.customizeCashbackStrategy(new CashbackStrategyCustomizationVO(orderId, cashbackStrategy));
	}

	@Override
	@Transactional
	public boolean updateOrderContractApprovalStatus(int orderId, ModifiableApprovalStatus approvalStatus) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if (order.getOrderStatus() != OrderStatus.CONSULTING)
			throw new ResourceForbiddenException("Order status must be " + OrderStatus.CONSULTING + "!");
		OrderDocIdApprovalStatusDO simplifiedOrderContract = checkExistenceOfOrderContract(orderId);
		orderContractDAO.updateApprovalStatus(orderId, approvalStatus.toApprovalStatus());
		if (simplifiedOrderContract.getApprovalStatus() != ApprovalStatus.APPROVED
				&& approvalStatus.toApprovalStatus() == ApprovalStatus.APPROVED)
			orderDAO.updateStatusContractStatus(orderId, OrderStatus.RESERVED, approvalStatus.toApprovalStatus());
		else
			orderDAO.updateStatusContractStatus(orderId, OrderStatus.CONSULTING, approvalStatus.toApprovalStatus());

		notificationService
		.sendMessages(new OrderContractStatusUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());

		return true;
	}

	@Override
	@Transactional
	public boolean updateOrderPaymentApprovalStatus(int orderId, ModifiableApprovalStatus approvalStatus) {
		OrderIdParticipantStatusDO order = checkExistenceOfOrder(orderId);
		if (order.getOrderStatus() != OrderStatus.FEASTED)
			throw new ResourceForbiddenException("Order status must be " + OrderStatus.FEASTED + "!");
		OrderPaymentPaidApprovalStatusDO simplifiedOrderPayment = checkExistenceOfOrderPayment(orderId);
		orderPaymentDAO.updateApprovalStatus(orderId, approvalStatus.toApprovalStatus());
		if (simplifiedOrderPayment.getApprovalStatus() != ApprovalStatus.APPROVED
				&& approvalStatus.toApprovalStatus() == ApprovalStatus.APPROVED) {
			orderDAO.updateStatusPaymentStatus(orderId, OrderStatus.CASHBACK, approvalStatus.toApprovalStatus());
			if (order != null) {
				cashbackService.findCashbackStrategyByOrderId(orderId).ifPresent(strategy -> {
					double totalPayment = simplifiedOrderPayment.getAmountPaid();
					CashbackStrategyDTO strategyDTO = strategy.getCashbackStrategy();
					userInfoService.updateCashbackInfo(order.getCustomerId(), 0,
							totalPayment * strategyDTO.getPercentageForCustomer() / 100);
					userInfoService.updateCashbackInfo(order.getManagerId(), 0,
							totalPayment * strategyDTO.getPercentageForManager() / 100);
					userInfoService.updateCashbackInfo(order.getRecommenderId(), 0,
							totalPayment * strategyDTO.getPercentageForRecommender() / 100);
				});
			}
		} else if (simplifiedOrderPayment.getApprovalStatus() != ApprovalStatus.APPROVED)
			orderDAO.updateStatusPaymentStatus(orderId, OrderStatus.FEASTED, approvalStatus.toApprovalStatus());

		notificationService
		.sendMessages(new OrderPaymentStatusUpdatedMessage(getOrderMessageEntity(order)).getOutgoingMessage());

		return true;
	}

	private List<RoleInOrder> getRoleInOrder(int userId, int customerId, int recommenderId, int managerId) {
		List<RoleInOrder> roles = new ArrayList<>();
		if (userId == customerId)
			roles.add(RoleInOrder.CUSTOMER);
		if (userId == recommenderId)
			roles.add(RoleInOrder.RECOMMENDER);
		if (userId == managerId)
			roles.add(RoleInOrder.MANAGER);
		return roles;
	}

	private List<OrderStatus> getOrderStatusCollection(OrderStatusVO vo) {
		switch (vo) {
		case FINISHED:
			return Arrays.asList(OrderStatus.DONE);
		case UNFINISHED:
			return OrderStatus.unfinishedStatus();
		}
		return new ArrayList<>();
	}

	private OrderMessageEntity getOrderMessageEntity(OrderIdParticipantStatusDO order) {
		return new OrderMessageEntity(order.getId(), order.getHotel(), order.getCustomer(), order.getRecommender(),
				order.getManager(), order.getOrderStatus().getCategory());
	}

	@Override
	public Page<SimplifiedOrderForAdminDTO> listOrdersForAdmin(OrderQueryVO orderQueryVO, Pageable pageable) {
		QuerySepcification<OrderDO> spec = new QuerySepcification<>(queryBuilder.create(orderQueryVO));
		try {
			return orderDAO.findAll(spec, pageable).map(orderDO -> new SimplifiedOrderForAdminDTO(orderDO.getId(),
					dateFormatter.toOrderCreationTime(orderDO.getOrderTime()), String.valueOf(orderDO.getOrderNumber()),
					orderDO.getBanquetHallId(), orderDO.getBanquetHall(), orderDO.getHotelId(), orderDO.getHotel(),
					orderDO.getCustomer(), orderDO.getRecommender(), orderDO.getContact(), orderDO.getOrderStatus(),
					dateFormatter.toOrderLastUpdated(orderDO.getLastUpdated())));
		} catch (PropertyReferenceException e) {
			throw new InvalidRequestParametersException(e.getMessage());
		}
	}
}
